home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
090
/
cmln1086.arc
/
HOLOGRAM.PAS
< prev
Wrap
Pascal/Delphi Source File
|
1986-07-29
|
16KB
|
457 lines
{ PROGRAM: ALIAS.PAS
AUTHOR: Brian Hayes
Copyright 1986 Brian Hayes
May be freely copied for noncommercial use.
VERSION: 1.0
DATE BEGUN: June 28, 1986
LAST REVISION: June 30, 1986
FOR COMPILATION BY: Turbo Pascal v. 3.0 (8087 recommended)
"INCLUDE" FILES: None
}
{ DESCRIPTION:
Accepts a polynomial in two unknowns (X and Y) and plots
a contour map of Z as a function of X and Y over a specified
range of values. The contour map is printed on a dot-matrix
printer. The function is "rasterized" by evaluating it at
every point in the map and turning the corresponding pixel
on only if the resulting value, modulo the contour interval,
falls in a specified range. If the surface is steep enough
that there are fewer than two dots per contour interval,
aliasing results.
}
{ COMPILER DIRECTIVES: }
{$B+} {B+ assigns StdIn/StdOut to CON, B- to TRM; default +}
{$C-} {C+ allows ^C and ^S during Read/ReadLn; default +}
{$I+} {I+ enables automatic I/O error checking; default +}
{$R-} {R+ enables run-time checking of index bounds; default -}
{$V-} {V+ requires string parameters to match declared length; default +}
{$U-} {U+ allows ^C interrupt at any time; default -}
{ $G0} {G>0 allocates input buffer for I/O redirection; default 0}
{ $P0} {P>0 allocates output buffer for I/O redirection; default 0}
{$D+} {D+ unbuffers I/O for devices; default +}
{$F16} {Fn sets maximum number of files open simultaneously; default 16}
{$K-} {K+ enables checking for stack-heap collision; default +}
program HOLOGRAM;
type
TermPtr = ^TermType;
TermType = record { Stores data on each term of the polynomial }
Coef : real;
Xpower : integer;
Ypower : integer;
NextTerm : TermPtr;
end;
RegPack = record
AL, AH, BL, BH, CL, CH, DL, DH : byte
end;
InputStrType = string[80];
var
FirstTerm : TermPtr; { Start of chain of terms }
Regs : RegPack; { For MsDos call }
MinXY : real; { Upper left corner }
MaxXY : real; { Lower right corner }
MapSize : real; { In inches; always square }
ContIntv : real; { Contour interval }
ContWidth : real; { Fraction of black/white }
Istr : InputStrType; { Holds input equation }
Answer : char; { For main program query }
{ For a description of the following integer-power algorithm and
a historical account of its development see Dennis E. Hamilton,
"Fast Integer Powers for Pascal," Dr. Dobbs Journal, February,
1986, page 36. }
function Power(N : real; E : integer): real;
var
TmpN : real;
TmpE : integer;
begin
case E of
0 : Power := 1.0; { NOTE : 0^0 error ignored }
1 : Power := N;
2 : Power := Sqr(N);
else begin
TmpE := Abs(E);
while not Odd(TmpE) do
begin
N := Sqr(N); TmpE := TmpE shr 1;
end;
TmpN := N;
while TmpE > 1 do
begin
repeat
N := Sqr(N); TmpE := TmpE shr 1;
until Odd(TmpE);
TmpN := TmpN * N;
end;
if E < 0 then Power := 1.0/TmpN else Power := TmpN;
end;
end;
end;
function EvalPoly(X, Y : real) : real;
{ Evaluates the equation accepted by GetPoly at a single point. The
terms of the equation are stored in a linked list, which is traced
by EvalPoly. For each term the coefficient and the exponents stored
in the list node are applied, and the result is added to the running
total of all terms evaluated so far. (Negative terms are handled by
negating the coefficient in GetPoly.) When there are no more terms,
the function returns the total value. }
var
Next : TermPtr;
Value : real;
begin
Next := FirstTerm;
Value := 0.0;
while Next <> nil do
begin
if Next^.Coef <> 0.0 then Value :=
Value + (Next^.Coef * Power(X,Next^.Xpower) * Power(Y,Next^.Ypower));
Next := Next^.NextTerm;
end;
EvalPoly := Value;
end;
procedure GetPoly;
{ GetPoly accepts an equation from the user and parses it into terms;
each term consists of a coefficient and two exponents (for the X and
Y factors). If X or Y is absent from the term, an exponent of zero is
stored. If both X and Y are missing (i.e., the term is a constant),
two zero exponents are stored. Note that any nonzero value raised to
the zero power is 1. }
{ The format for an equation can be defined by the following example:
Z = 2X^3Y^4 - 5XY + 6Y^7 - 8
Each term can have one coefficient, which must be the
first item in the term and must be recognizable by Turbo
Pascal as a real number. If the coefficient is omitted,
it is assumed to be 1. An exponents is an integer that
follows an X or a Y and a caret; missing exponents are
set to zero. Terms are separated by plus or minus signs. }
{ WARNING: This routine is not robust. It is a quick-and-dirty
equation parser, which handles only simple polynomials
in X and Y. Useful extensions would include the ability
to recognize parenthesized expressions, the division
operator, fractional exponents and trig functions. It
would also be convenient to enter equations in polar
coordinates. Finally, the error handling included here
is very crude. }
type
PolyStrType = string[82];
ErrorCode = (BadCoef, BadExp);
var
Pstr : PolyStrType; { Holds equation string }
This : TermPtr; { Current term pointer }
Old : TermPtr; { Needed to forge links }
I : 0..83; { Index into Pstr }
ParseMore : boolean; { Flag to exit loop }
ErrorFlag : boolean; { Flag to mark error }
procedure Error(Err: ErrorCode);
begin
ParseMore := False; { Exit GetPoly }
if not ErrorFlag then { Report 1st error only }
begin
ErrorFlag := True;
case Err of
BadCoef : WriteLn('Bad coefficient.');
BadExp : WriteLn('Bad exponent.');
end;
end;
end;
function GetChar : char; { Get and remove next char from string }
begin
I := Succ(I); { Must not be called at end of string }
GetChar := Pstr[I];
end;
function NextChar : char; { Peek at next but don't remove }
begin
NextChar := Pstr[Succ(I)];
end;
procedure GetCoef;
var
CoefSign : char;
CoefStr : InputStrType;
CoefVal : real;
ConvCode : integer;
begin
if not (NextChar in ['+','-']) then Error(BadCoef); { Must have sign }
while (NextChar in ['+','-']) do CoefSign := GetChar; { Use last sign }
CoefStr := '';
while (NextChar in ['0'..'9','.']) do CoefStr := CoefStr + GetChar;
if (CoefStr = '') then This^.Coef := 1.0
else
begin
Val(CoefStr, CoefVal, ConvCode);
if ConvCode = 0 then This^.Coef := CoefVal else Error(BadCoef);
end;
if CoefSign = '-' then This^.Coef := -This^.Coef;
if (NextChar = #00) then ParseMore := False; { End of string }
end;
procedure GetX;
var
Dump : char;
ExpSign : char;
ExpStr : InputStrType;
ExpVal : integer;
ConvCode : integer;
begin
if (NextChar in ['X','x']) then
begin
Dump := GetChar;
if (NextChar = '^') then
begin
Dump := GetChar;
while (NextChar in ['+','-']) do ExpSign := GetChar;
ExpStr := '';
while (NextChar in ['0'..'9']) do ExpStr := ExpStr + GetChar;
if ExpStr = '' then Error(BadExp);
Val(ExpStr, ExpVal, ConvCode);
if ConvCode = 0 then This^.Xpower := ExpVal else Error(BadExp);
if ExpSign = '-' then This^.Xpower := -This^.Xpower;
end
else This^.Xpower := 1
end;
if (NextChar = #00) then ParseMore := False;
end;
procedure GetY;
var
Dump : char;
ExpSign : char;
ExpStr : InputStrType;
ExpVal : integer;
ConvCode : integer;
begin
if (NextChar in ['Y','y']) then
begin
Dump := GetChar;
if (NextChar = '^') then
begin
Dump := GetChar;
while (NextChar in ['+','-']) do ExpSign := GetChar;
ExpStr := '';
while (NextChar in ['0'..'9']) do ExpStr := ExpStr + GetChar;
if ExpStr = '' then Error(BadExp);
Val(ExpStr, ExpVal, ConvCode);
if ConvCode = 0 then This^.Ypower := ExpVal else Error(BadExp);
if ExpSign = '-' then This^.Ypower := -This^.Ypower;
end
else This^.Ypower := 1
end;
if (NextChar = #00) then ParseMore := False;
end;
procedure NewTerm;
begin
Old := This; { Save pointer so we can add link }
New(This);
Old^.NextTerm := This; { Link to the new record }
This^.Coef := 0.0;
This^.Xpower := 0;
This^.Ypower := 0;
This^.NextTerm := nil; { Mark this as last term in chain }
end;
begin { GetPoly }
repeat
ErrorFlag := False;
WriteLn('Enter a polynomial in X and Y (e.g., Z = 2X^2 - Y + 5):');
WriteLn;
Write('Z = ');
ReadLn(Istr); { Get equation from terminal }
WriteLn; WriteLn;
Pstr := '+' + Istr + #00; { Precede first term with plus sign }
for I := 1 to Length(Pstr) do { Remove spaces }
if (Pstr[I] = #32) then Delete(Pstr, I, 1);
Mark(This);
NewTerm;
FirstTerm := This; { Set pointer to start of chain }
I := 0;
ParseMore := True;
repeat
if ParseMore then GetCoef;
if ParseMore then GetX;
if ParseMore then GetY;
if ParseMore then NewTerm;
until not ParseMore;
until ErrorFlag = False;
end;
procedure GetParams;
begin
repeat
Write('Range of X and Y to be plotted: From ');
Read(MinXY);
Write(' to ');
ReadLn(MaxXY);
WriteLn;
until (MaxXY <> MinXY);
repeat
Write('Map size (inches): ');
ReadLn(MapSize);
WriteLn;
until (MapSize > 0.25);
repeat
Write('Contour interval: ');
ReadLn(ContIntv);
WriteLn;
until (ContIntv > 0);
repeat
Write('Contour width (between 0.0 and 1.0): ');
ReadLn(ContWidth);
WriteLn;
until ((ContWidth > 0.0) and (ContWidth < 1.0));
end;
function GetInput: boolean;
var
Yes : char;
begin
GetInput := False;
ClrScr;
WriteLn('CONTOUR MAPPING');
WriteLn; WriteLn;
GetPoly;
GetParams;
Write('Proceed to plot map? (Y/N) ');
repeat
Read(Kbd, Yes);
if UpCase(Yes) = 'Y' then GetInput := True;
until UpCase(Yes) in ['Y','N'];
end;
procedure PrintByte(Pattern : byte);
{ The Pascal WRITE procedure does not accept a parameter of type byte.
This call on MsDos outputs a byte directly to the printer. Changes
will be needed for other operating systems. }
begin
Regs.AH := 5; { Printer output service }
Regs.DL := Pattern; { Byte to be printed }
MsDos(Regs);
end;
{ NOTE: The following procedures include escape codes for the
graphics modes of the Epson LQ-1500 printer. Other
Epson printers use similar (if not identical codes). }
procedure PrintBorder(N1, N2, Pattern : byte);
{ Prints a full line of a single graphics pattern. Used to produce
a single row of dots top and bottom as a border for the map. }
var
Row : integer;
begin
Write(Lst,#27'*'#0); { Graphics mode zero }
PrintByte(N1); PrintByte(N2); { Number of bytes to print }
for Row := 0 to (Integer(N2*256+N1)) do PrintByte(Pattern);
WriteLn(Lst);
end;
procedure PrintInfo;
begin
Write(Lst, 'Z = ');
WriteLn(Lst, Istr);
WriteLn(Lst);
WriteLn(Lst, 'From: ', MinXY:10:2);
WriteLn(Lst, 'To: ', MaxXY:10:2);
WriteLn(Lst, 'Interval: ', ContIntv:10:2);
WriteLn(Lst, 'Width: ', ContWidth:10:2);
WriteLn(Lst);
WriteLn(Lst);
end;
procedure PlotMap;
var
I, J, K : integer; { Loop indices }
X, Y : real; { Map coordinates }
Delta : real; { Change in X or Y per dot interval }
Dots : integer; { Number of dots per line }
N1, N2 : byte; { Number of dots in Epson format }
ThisByte : byte; { Holds pattern as points calculated }
begin
Dots := (Trunc(MapSize * 60) and $FFF8); { (div 8) cols & rows }
N1 := Byte(Lo(Dots + 2)); { Cols per row, in... }
N2 := Byte(Hi(Dots + 2)); { ... Epson format }
Delta := (MaxXY - MinXY) / Dots; { Z(X,Y) sample points }
PrintInfo; { Identify the plot }
WriteLn(Lst,#27'3'#24); { Set 24/180 linespace }
PrintBorder(N1, N2, 1); { Top border }
for K := 0 to ((Dots div 8) - 1) do { Each K one line }
begin
Write(Lst,#27'*'#0); { 60 dpi graphics }
PrintByte(N1); PrintByte(N2); { Number of bytes }
PrintByte(255); { Left border }
for J := 0 to (Dots - 1) do { Each J one column }
begin
ThisByte := 0; { Initially all blank }
for I := 0 to 7 do { Each I one dot }
begin
X := MinXY + (J * Delta); { }
Y := MinXY + ((K * 8 + I) * Delta); { SEE }
if (Frac(Abs((EvalPoly(X,Y))/ContIntv)) < ContWidth) { NOTE }
then ThisByte := ThisByte or 128 shr I; { }
end;
PrintByte(ThisByte); { Print one column }
end;
PrintByte(255); { Right border }
WriteLn(Lst); { CR/LF to printer }
if Keypressed then begin Read(Kbd); exit end; { Allow user break }
end;
PrintBorder(N1, N2, 128); { Bottom border }
WriteLn(Lst,#27'2'); { Reset line spacing }
end;
begin { Main Program }
repeat
if GetInput then PlotMap;
WriteLn; WriteLn; WriteLn;
Write('<P>lot another. <Q>uit.');
repeat Read(Kbd, Answer) until UpCase(Answer) in ['P','Q'];
until UpCase(Answer) = 'Q';
end.